{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "<center>\n",
    "<img src=\"https://raw.githubusercontent.com/Yorko/mlcourse.ai/master/img/ods_stickers.jpg\" />\n",
    "    \n",
    "## [mlcourse.ai](https://mlcourse.ai) - Open Machine Learning Course\n",
    "\n",
    "<center>\n",
    "Auteur: [Egor Polusmak](https://www.linkedin.com/in/egor-polusmak/).  \n",
    "Traduit et édité par [Yuanyuan Pao](https://www.linkedin.com/in/yuanyuanpao/) et [Ousmane Cissé](https://fr.linkedin.com/in/ousmane-cisse).  \n",
    "Ce matériel est soumis aux termes et conditions de la licence [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/).   \n",
    "L'utilisation gratuite est autorisée à des fins non commerciales."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "# <center>Topic 9. Analyse des séries temporelles en Python</center>\n",
    "## <center>Partie 2. Prédire l'avenir avec Facebook Prophet</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La prévision de séries chronologiques trouve une large application dans l'analyse de données. Ce ne sont que quelques-unes des prévisions imaginables des tendances futures qui pourraient être utiles:\n",
    "- Le nombre de serveurs dont un service en ligne aura besoin l'année prochaine.\n",
    "- La demande d'un produit d'épicerie dans un supermarché un jour donné.\n",
    "- Le cours de clôture de demain d'un actif financier négociable.\n",
    "\n",
    "Pour un autre exemple, nous pouvons faire une prédiction des performances d'une équipe, puis l'utiliser comme référence: d'abord pour fixer des objectifs pour l'équipe, puis pour mesurer les performances réelles de l'équipe par rapport à la référence.\n",
    "\n",
    "Il existe plusieurs méthodes différentes pour prédire les tendances futures, par exemple, [ARIMA](https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average), [ARCH](https://en.wikipedia.org/wiki/Autoregressive_conditional_heteroskedasticity), [modèles régressifs](https://en.wikipedia.org/wiki/Autoregressive_model), [réseaux de neurones](https://medium.com/machine-learning-world/neural-networks-for-algorithmic-trading-1-2-correct-time-series-forecasting-backtesting-9776bfd9e589).\n",
    "\n",
    "Dans cet article, nous examinerons [Prophet](https://facebook.github.io/prophet/), une bibliothèque de prévisions de séries chronologiques publiée par Facebook et open source, le 23 février 2017. Nous l'essayerons également dans le problème de prédiction du nombre quotidien de publications sur Medium."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## Plan de l'article\n",
    "\n",
    "1. Introduction\n",
    "2. Le modèle de prévision de Prophet\n",
    "3. Entraînez-vous avec le Prophet\n",
    "    * 3.1 Installation en Python\n",
    "    * 3.2 Ensemble de données\n",
    "    * 3.3 Analyse visuelle exploratoire\n",
    "    * 3.4 Faire une prévision\n",
    "    * 3.5 Évaluation de la qualité des prévisions\n",
    "    * 3.6 Visualisation\n",
    "4. Transformation Box-Cox\n",
    "5. Résumé\n",
    "6. Références"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 1. Introduction\n",
    "\n",
    "Selon [l'article](https://research.fb.com/prophet-forecasting-at-scale/) sur Facebook Research, Prophet a été initialement développé dans le but de créer des prévisions commerciales de haute qualité. Cette bibliothèque tente de résoudre les difficultés suivantes, communes à de nombreuses séries chronologiques commerciales:\n",
    "- Effets saisonniers causés par le comportement humain: cycles hebdomadaires, mensuels et annuels, creux et pics les jours fériés.\n",
    "- Changements de tendance dus aux nouveaux produits et aux événements du marché.\n",
    "- Valeurs aberrantes.\n",
    "\n",
    "Les auteurs affirment que, même avec les paramètres par défaut, dans de nombreux cas, leur bibliothèque produit des prévisions aussi précises que celles fournies par des analystes expérimentés.\n",
    "\n",
    "De plus, Prophet dispose d'un certain nombre de personnalisations intuitives et facilement interprétables qui permettent d'améliorer progressivement la qualité du modèle de prévision. Ce qui est particulièrement important, ces paramètres sont tout à fait compréhensibles même pour les non-experts en analyse de séries chronologiques, qui est un domaine de la science des données nécessitant certaines compétences et expérience.\n",
    "\n",
    "Soit dit en passant, l'article d'origine s'intitule «Prévisions à grande échelle», mais il ne s'agit pas de l'échelle au sens «habituel», qui traite des problèmes de calcul et d'infrastructure d'un grand nombre de programmes de travail. Selon les auteurs, Prophet devrait bien évoluer dans les 3 domaines suivants:\n",
    "- Accessibilité à un large public d'analystes, éventuellement sans expertise approfondie des séries chronologiques.\n",
    "- Applicabilité à un large éventail de problèmes de prévision distincts.\n",
    "- Estimation automatisée des performances d'un grand nombre de prévisions, y compris la signalisation des problèmes potentiels pour leur inspection ultérieure par l'analyste."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 2. Le modèle de prévision Prophet\n",
    "\n",
    "Maintenant, regardons de plus près comment fonctionne Prophet. Dans son essence, cette bibliothèque utilise le [modèle de régression additive](https://en.wikipedia.org/wiki/Additive_model) $y(t)$ comprenant les composants suivants:\n",
    "\n",
    "$$y(t) = g(t) + s(t) + h(t) + \\epsilon_{t},$$\n",
    "\n",
    "où:\n",
    "* La tendance $g(t)$ modélise les changements non périodiques.\n",
    "* La saisonnalité $s(t)$ représente des changements périodiques.\n",
    "* La composante vacances $h(t)$ fournit des informations sur les vacances et les événements.\n",
    "\n",
    "Ci-dessous, nous considérerons quelques propriétés importantes de ces composants de modèle."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Tendance\n",
    "\n",
    "La bibliothèque Prophet implémente deux modèles de tendance possibles pour $g(t)$.\n",
    "\n",
    "Le premier est appelé *Croissance saturée non linéaire*. Il est représenté sous la forme du [modèle de croissance logistique](https://en.wikipedia.org/wiki/Fonction_logistique):\n",
    "\n",
    "$$g(t) = \\frac{C}{1+e^{-k(t - m)}},$$\n",
    "\n",
    "où:\n",
    "\n",
    "* $C$ est la capacité de charge (c'est-à-dire la valeur maximale de la courbe).\n",
    "\n",
    "* $k$ est le taux de croissance (qui représente \"la pente\" de la courbe).\n",
    "\n",
    "* $m$ est un paramètre de décalage.\n",
    "\n",
    "Cette équation logistique permet de modéliser la croissance non linéaire avec saturation, c'est-à-dire lorsque le taux de croissance d'une valeur diminue avec sa croissance. Un des exemples typiques serait de représenter la croissance de l'audience d'une application ou d'un site Web.\n",
    "\n",
    "En fait, $C$ et $k$ ne sont pas nécessairement des constantes et peuvent varier dans le temps. Prophet prend en charge le réglage automatique et manuel de leur variabilité. La bibliothèque peut elle-même choisir des points optimaux de changements de tendance en ajustant les données historiques fournies.\n",
    "\n",
    "En outre, Prophet permet aux analystes de définir manuellement des points de changement du taux de croissance et des valeurs de capacité à différents moments. Par exemple, les analystes peuvent avoir des informations sur les dates des versions précédentes qui ont influencé de manière importante certains indicateurs clés de produit.\n",
    "\n",
    "Le deuxième modèle de tendance est un simple *modèle linéaire par morceaux* (Piecewise Linear Model) avec un taux de croissance constant.  \n",
    "Il est le mieux adapté aux problèmes sans saturation de la croissance."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Saisonnalité\n",
    "\n",
    "La composante saisonnière $s(t)$ fournit un modèle flexible de changements périodiques dus à la saisonnalité hebdomadaire et annuelle.\n",
    "\n",
    "Les données saisonnières hebdomadaires sont modélisées avec des variables factices. Six nouvelles variables sont ajoutées: «lundi», «mardi», «mercredi», «jeudi», «vendredi», «samedi», qui prennent des valeurs 0 ou 1 selon le jour de la semaine. La caractéristique «dimanche» n'est pas ajoutée car ce serait une combinaison linéaire des autres jours de la semaine, et ce fait aurait un effet négatif sur le modèle.\n",
    "\n",
    "Le modèle de saisonnalité annuelle dans Prophet repose sur la série de Fourier.\n",
    "\n",
    "Depuis la version 0.2, vous pouvez également utiliser des séries chronologiques infra-journalières et faire des prévisions infra-journalières, ainsi qu'utiliser la nouvelle caractéristique de saisonnalité quotidienne."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Vacances et événements\n",
    "\n",
    "La composante $h(t)$ représente les jours anormaux prévisibles de l'année, y compris ceux dont les horaires sont irréguliers, par exemple les Black Fridays.\n",
    "\n",
    "Pour utiliser cette caractéristique, l'analyste doit fournir une liste personnalisée d'événements."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Erreur\n",
    "\n",
    "Le terme d'erreur $\\epsilon(t)$ représente des informations qui n'étaient pas reflétées dans le modèle. Habituellement, il est modélisé comme un bruit normalement distribué."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Analyse comparative (benchmark) de Prophet\n",
    "\n",
    "Pour une description détaillée du modèle et des algorithmes derrière Prophet, reportez-vous à l'article [\"Forecasting at scale\"](https://peerj.com/preprints/3190/) de Sean J. Taylor et Benjamin Letham.\n",
    "\n",
    "Les auteurs ont également comparé leur bibliothèque avec plusieurs autres méthodes de prévision de séries chronologiques. Ils ont utilisé l'[Erreur absolue moyenne en pourcentage (MAPE)](https://en.wikipedia.org/wiki/Mean _absolue_ pourcentage_erreur) comme mesure de la précision de la prédiction. Dans cette analyse, Prophet a montré une erreur de prévision considérablement plus faible que les autres modèles."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"https://raw.githubusercontent.com/Yorko/mlcourse.ai/master/img/topic9_benchmarking_prophet.png\" />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Regardons de plus près comment la qualité de la prévision a été mesurée dans l'article. Pour ce faire, nous aurons besoin de la formule d'erreur moyenne absolue en pourcentage.\n",
    "\n",
    "Soit $y_{i}$ la *valeur réelle (historique)* et $\\hat{y}_{i}$ la *valeur prévue* donnée par notre modèle.\n",
    "\n",
    "$e_{i} = y_{i} - \\hat{y}_{i}$ est alors *l'erreur de prévision* et $p_{i} =\\frac{\\displaystyle e_{i}}{\\displaystyle y_{i}}$ est *l'erreur de prévision relative*.\n",
    "\n",
    "Nous définissons\n",
    "\n",
    "$$MAPE = mean\\big(\\left |p_{i} \\right |\\big)$$\n",
    "\n",
    "MAPE est largement utilisé comme mesure de la précision des prédictions car il exprime l'erreur en pourcentage et peut donc être utilisé dans les évaluations de modèles sur différents ensembles de données.\n",
    "\n",
    "De plus, lors de l'évaluation d'un algorithme de prévision, il peut s'avérer utile de calculer [MAE (Mean Absolute Error)](https://en.wikipedia.org/wiki/Mean _error_ absolue) afin d'avoir une image des erreurs en nombres absolus. En utilisant des composants précédemment définis, son équation sera\n",
    "\n",
    "$$MAE = mean\\big(\\left |e_{i}\\right |\\big)$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Quelques mots sur les algorithmes avec lesquels Prophet a été comparé. La plupart d'entre eux sont assez simples et sont souvent utilisés comme référence pour d'autres modèles:\n",
    "* `naive` est une approche de prévision simpliste dans laquelle nous prédisons toutes les valeurs futures en nous appuyant uniquement sur l'observation au dernier moment disponible.\n",
    "* `snaive` (saisonnier naïf) est un modèle qui fait des prédictions constantes en tenant compte des informations sur la saisonnalité. Par exemple, dans le cas de données saisonnières hebdomadaires pour chaque futur lundi, nous prédirions la valeur du dernier lundi et pour tous les futurs mardis, nous utiliserions la valeur du dernier mardi, etc.\n",
    "* `mean` utilise la valeur moyenne des données comme prévision.\n",
    "* `arima` signifie *Autoregressive Integrated Moving Average*, voir [Wikipedia](https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average) pour plus de détails.\n",
    "* `ets` signifie *Lissage exponentiel*, voir [Wikipedia](https://en.wikipedia.org/wiki/Exponential_smoothing) pour plus d'informations."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 3. Entraînez-vous avec Facebook Prophet\n",
    "\n",
    "### 3.1 Installation en Python\n",
    "\n",
    "Tout d'abord, vous devez installer la bibliothèque. Prophet est disponible pour Python et R. Le choix dépendra de vos préférences personnelles et des exigences du projet. Plus loin dans cet article, nous utiliserons Python.\n",
    "\n",
    "En Python, vous pouvez installer Prophet à l'aide de PyPI:\n",
    "```\n",
    "$ pip install fbprophet\n",
    "```\n",
    "\n",
    "Dans R, vous trouverez le package CRAN correspondant. Reportez-vous à la [documentation](https://facebookincubator.github.io/prophet/docs/installation.html) pour plus de détails.\n",
    "\n",
    "Importons les modules dont nous aurons besoin et initialisons notre environnement:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import statsmodels.api as sm\n",
    "from scipy import stats\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 3.2 Jeu de données\n",
    "\n",
    "Nous prédirons le nombre quotidien de publications publiées sur [Medium](https://medium.com/).\n",
    "\n",
    "Tout d'abord, nous chargeons notre jeu de données."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv(\"../../data/medium_posts.csv.zip\", sep=\"\\t\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Ensuite, nous omettons toutes les colonnes à l'exception de `published` et `url`. Le premier correspond à la dimension temporelle tandis que le second identifie de manière unique un message par son URL. Par la suite, nous nous débarrassons des doublons possibles et des valeurs manquantes dans les données:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df[[\"published\", \"url\"]].dropna().drop_duplicates()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Ensuite, nous devons convertir `published` au format datetime car par défaut `pandas` traite ce champ comme une chaîne."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"published\"] = pd.to_datetime(df[\"published\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Trions la trame de données par date et jetons un œil à ce que nous avons:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.sort_values(by=[\"published\"]).head(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La date de sortie publique de Medium était le 15 août 2012. Mais, comme vous pouvez le voir sur les données ci-dessus, il existe au moins plusieurs lignes avec des dates de publication beaucoup plus anciennes. Ils sont apparus d'une manière ou d'une autre dans notre ensemble de données, mais ils ne sont guère légitimes. Nous allons simplement couper notre série chronologique pour ne conserver que les lignes qui tombent sur la période du 15 août 2012 au 25 juin 2017:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df[\n",
    "    (df[\"published\"] > \"2012-08-15\") & (df[\"published\"] < \"2017-06-26\")\n",
    "].sort_values(by=[\"published\"])\n",
    "df.head(n=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.tail(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Comme nous allons prédire le nombre de publications, nous allons agréger et compter les publications uniques à chaque moment donné. Nous nommerons la nouvelle colonne correspondante `posts`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "aggr_df = df.groupby(\"published\")[[\"url\"]].count()\n",
    "aggr_df.columns = [\"posts\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Dans cette pratique, nous sommes intéressés par le nombre de messages **par jour**. Mais en ce moment, toutes nos données sont divisées en intervalles de temps irréguliers qui sont inférieurs à une journée. C'est ce qu'on appelle une série chronologique infra-journalière (*sub-daily time series*). Pour le voir, affichons les 3 premières lignes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "aggr_df.head(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Pour résoudre ce problème, nous devons agréger le nombre de messages par \"bins\" d'une taille de date. Dans l'analyse des séries chronologiques, ce processus est appelé *rééchantillonnage* (*resampling*). Et si l'on *réduit* le taux d'échantillonnage des données, il est souvent appelé *sous-échantillonnage* (*downsampling*).\n",
    "\n",
    "Heureusement, `pandas` a une fonctionnalité intégrée pour cette tâche. Nous allons rééchantillonner notre indice de date jusqu'à des \"bins\" d'un jour:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "daily_df = aggr_df.resample(\"D\").apply(sum)\n",
    "daily_df.head(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 3.3 Analyse visuelle exploratoire\n",
    "\n",
    "Comme toujours, il peut être utile et instructif de regarder une représentation graphique de vos données.\n",
    "\n",
    "Nous allons créer un tracé de série chronologique pour toute la plage de temps. L'affichage de données sur une période aussi longue peut donner des indices sur la saisonnalité et les écarts anormaux visibles.\n",
    "\n",
    "Tout d'abord, nous importons et initialisons la bibliothèque `Plotly`, qui permet de créer de superbes graphes interactifs:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotly import graph_objs as go\n",
    "from plotly.offline import init_notebook_mode, iplot\n",
    "\n",
    "# Initialize plotly\n",
    "init_notebook_mode(connected=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous définissons également une fonction d'aide, qui tracera nos trames de données tout au long de l'article:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plotly_df(df, title=\"\"):\n",
    "    \"\"\"Visualize all the dataframe columns as line plots.\"\"\"\n",
    "    common_kw = dict(x=df.index, mode=\"lines\")\n",
    "    data = [go.Scatter(y=df[c], name=c, **common_kw) for c in df.columns]\n",
    "    layout = dict(title=title)\n",
    "    fig = dict(data=data, layout=layout)\n",
    "    iplot(fig, show_link=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Essayons de tracer notre jeu de données *tel quel*:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plotly_df(daily_df, title=\"Posts on Medium (daily)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Les données à haute fréquence peuvent être assez difficiles à analyser. Même avec la possibilité de zoomer fournie par `Plotly`, il est difficile d'inférer quoi que ce soit de significatif à partir de ce graphique, à l'exception de la tendance à la hausse et à l'accélération.\n",
    "\n",
    "Pour réduire le bruit, nous allons rééchantillonner le compte à rebours des postes jusqu'à la semaine. Outre le *binning*, d'autres techniques possibles de réduction du bruit incluent [Moving-Average Smoothing](https://en.wikipedia.org/wiki/Moving_average) et [Exponential Smoothing](https://en.wikipedia.org/wiki/Exponential_smoothing), entre autres.\n",
    "\n",
    "Nous sauvegardons notre dataframe sous-échantillonné dans une variable distincte, car dans cette pratique, nous ne travaillerons qu'avec des séries journalières:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "weekly_df = daily_df.resample(\"W\").apply(sum)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Enfin, nous traçons le résultat:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plotly_df(weekly_df, title=\"Posts on Medium (weekly)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Ce graphique sous-échantillonné s'avère un peu meilleur pour la perception d'un analyste.\n",
    "\n",
    "L'une des fonctions les plus utiles fournies par `Plotly` est la possibilité de plonger rapidement dans différentes périodes de la chronologie afin de mieux comprendre les données et trouver des indices visuels sur les tendances possibles, les effets périodiques et irréguliers.\n",
    "\n",
    "Par exemple, un zoom avant sur quelques années consécutives nous montre des points temporels correspondant aux vacances de Noël, qui influencent grandement les comportements humains.\n",
    "\n",
    "Maintenant, nous allons omettre les premières années d'observations, jusqu'en 2015. Premièrement, elles ne contribueront pas beaucoup à la qualité des prévisions en 2017. Deuxièmement, ces premières années, ayant un nombre très faible de messages par jour, sont susceptible d'augmenter le bruit dans nos prévisions, car le modèle serait obligé d'ajuster ces données historiques anormales avec des données plus pertinentes et indicatives des dernières années."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "daily_df = daily_df.loc[daily_df.index >= \"2015-01-01\"]\n",
    "daily_df.head(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Pour résumer, à partir de l'analyse visuelle, nous pouvons voir que notre ensemble de données n'est pas stationnaire avec une tendance croissante importante. Il montre également une saisonnalité hebdomadaire et annuelle et un certain nombre de jours anormaux chaque année."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 3.4 Faire une prévision\n",
    "\n",
    "L'API de Prophet est très similaire à celle que vous pouvez trouver dans `sklearn`. Nous créons d'abord un modèle, puis appelons la méthode `fit` et, enfin, faisons une prévision. L'entrée de la méthode `fit` est un` DataFrame` avec deux colonnes:\n",
    "* `ds` (datestamp ou horodatage) doit être de type` date` ou `datetime`.\n",
    "* `y` est une valeur numérique que nous voulons prédire.\n",
    "\n",
    "Pour commencer, nous allons importer la bibliothèque et éliminer les messages de diagnostic sans importance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import logging\n",
    "\n",
    "from fbprophet import Prophet\n",
    "\n",
    "logging.getLogger().setLevel(logging.ERROR)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Convertissons notre dataframe de données au format requis par Prophet:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = daily_df.reset_index()\n",
    "df.columns = [\"ds\", \"y\"]\n",
    "df.tail(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Les auteurs de la bibliothèque conseillent généralement de faire des prédictions basées sur au moins plusieurs mois, idéalement, plus d'un an de données historiques. Heureusement, dans notre cas, nous avons plus de quelques années de données pour s'adapter au modèle.\n",
    "\n",
    "Pour mesurer la qualité de nos prévisions, nous devons diviser notre ensemble de données en une *partie historique*, qui est la première et la plus grande tranche de nos données, et une *partie prédiction*, qui sera située à la fin de la chronologie. Nous allons supprimer le dernier mois de l'ensemble de données afin de l'utiliser plus tard comme cible de prédiction:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prediction_size = 30\n",
    "train_df = df[:-prediction_size]\n",
    "train_df.tail(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Maintenant, nous devons créer un nouvel objet `Prophet`. Ici, nous pouvons passer les paramètres du modèle dans le constructeur. Mais dans cet article, nous utiliserons les valeurs par défaut. Ensuite, nous formons notre modèle en invoquant sa méthode `fit` sur notre jeu de données de formation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = Prophet()\n",
    "m.fit(train_df);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "En utilisant la méthode `Prophet.make_future_dataframe`, nous créons un dataframe qui contiendra toutes les dates de l'historique et s'étendra également dans le futur pour les 30 jours que nous avons omis auparavant."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "future = m.make_future_dataframe(periods=prediction_size)\n",
    "future.tail(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous prédisons les valeurs avec `Prophet` en passant les dates pour lesquelles nous voulons créer une prévision. Si nous fournissons également les dates historiques (comme dans notre cas), en plus de la prédiction, nous obtiendrons un ajustement dans l'échantillon pour l'historique. Appelons la méthode `predict` du modèle avec notre dataframe `future` en entrée:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forecast = m.predict(future)\n",
    "forecast.tail(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Dans le dataframe résultant, vous pouvez voir de nombreuses colonnes caractérisant la prédiction, y compris les composants de tendance et de saisonnalité ainsi que leurs intervalles de confiance. La prévision elle-même est stockée dans la colonne `yhat`.\n",
    "\n",
    "La bibliothèque Prophet possède ses propres outils de visualisation intégrés qui nous permettent d'évaluer rapidement le résultat.\n",
    "\n",
    "Tout d'abord, il existe une méthode appelée `Prophet.plot` qui trace tous les points de la prévision:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m.plot(forecast);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Ce graphique n'a pas l'air très informatif. La seule conclusion définitive que nous pouvons tirer ici est que le modèle a traité de nombreux points de données comme des valeurs aberrantes.\n",
    "\n",
    "La deuxième fonction `Prophet.plot_components` pourrait être beaucoup plus utile dans notre cas. Il nous permet d'observer différentes composantes du modèle séparément: tendance, saisonnalité annuelle et hebdomadaire. De plus, si vous fournissez des informations sur les vacances et les événements à votre modèle, elles seront également affichées dans ce graphique.\n",
    "\n",
    "Essayons-le:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m.plot_components(forecast);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Comme vous pouvez le voir sur le graphique des tendances, Prophet a fait du bon travail en adaptant la croissance accélérée des nouveaux messages à la fin de 2016. Le graphique de la saisonnalité hebdomadaire conduit à la conclusion qu'il y a généralement moins de nouveaux messages le samedi et le dimanche que le les autres jours de la semaine. Dans le graphique de saisonnalité annuelle, il y a une baisse importante le jour de Noël."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 3.5 Évaluation de la qualité des prévisions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Évaluons la qualité de l'algorithme en calculant les mesures d'erreur pour les 30 derniers jours que nous avons prédits. Pour cela, nous aurons besoin des observations $y_i$ et des valeurs prédites correspondantes $\\hat{y}_i$.\n",
    "\n",
    "Examinons l'objet `forecast` que la bibliothèque a créé pour nous:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\", \".join(forecast.columns))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous pouvons voir que cette base de données contient toutes les informations dont nous avons besoin, à l'exception des valeurs historiques. Nous devons joindre l'objet `forecast` avec les valeurs réelles `y` de l'ensemble de données d'origine `df`. Pour cela nous allons définir une helper fonction que nous réutiliserons plus tard:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_comparison_dataframe(historical, forecast):\n",
    "    \"\"\"Join the history with the forecast.\n",
    "    \n",
    "       The resulting dataset will contain columns 'yhat', 'yhat_lower', 'yhat_upper' and 'y'.\n",
    "    \"\"\"\n",
    "    return forecast.set_index(\"ds\")[[\"yhat\", \"yhat_lower\", \"yhat_upper\"]].join(\n",
    "        historical.set_index(\"ds\")\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Appliquons cette fonction à notre dernière prévision:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cmp_df = make_comparison_dataframe(df, forecast)\n",
    "cmp_df.tail(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous allons également définir une helper fonction que nous utiliserons pour évaluer la qualité de nos prévisions avec les mesures d'erreur MAPE et MAE:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calculate_forecast_errors(df, prediction_size):\n",
    "    \"\"\"Calculate MAPE and MAE of the forecast.\n",
    "    \n",
    "       Args:\n",
    "           df: joined dataset with 'y' and 'yhat' columns.\n",
    "           prediction_size: number of days at the end to predict.\n",
    "    \"\"\"\n",
    "\n",
    "    # Make a copy\n",
    "    df = df.copy()\n",
    "\n",
    "    # Now we calculate the values of e_i and p_i according to the formulas given in the article above.\n",
    "    df[\"e\"] = df[\"y\"] - df[\"yhat\"]\n",
    "    df[\"p\"] = 100 * df[\"e\"] / df[\"y\"]\n",
    "\n",
    "    # Recall that we held out the values of the last `prediction_size` days\n",
    "    # in order to predict them and measure the quality of the model.\n",
    "\n",
    "    # Now cut out the part of the data which we made our prediction for.\n",
    "    predicted_part = df[-prediction_size:]\n",
    "\n",
    "    # Define the function that averages absolute error values over the predicted part.\n",
    "    error_mean = lambda error_name: np.mean(np.abs(predicted_part[error_name]))\n",
    "\n",
    "    # Now we can calculate MAPE and MAE and return the resulting dictionary of errors.\n",
    "    return {\"MAPE\": error_mean(\"p\"), \"MAE\": error_mean(\"e\")}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Utilisons notre fonction:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for err_name, err_value in calculate_forecast_errors(cmp_df, prediction_size).items():\n",
    "    print(err_name, err_value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "En conséquence, l'erreur relative de notre prévision (MAPE) est d'environ 22,72%, et en moyenne notre modèle est erroné de 70,45 posts (MAE)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 3.6 Visualisation\n",
    "\n",
    "Créons notre propre visualisation du modèle construit par Prophet. Il comprendra les valeurs réelles, les prévisions et les intervalles de confiance.\n",
    "\n",
    "Premièrement, nous allons tracer les données sur une période de temps plus courte pour rendre les points de données plus faciles à distinguer. Deuxièmement, nous ne montrerons les performances du modèle que pour la période que nous avons prévue, c'est-à-dire les 30 derniers jours. Il semble que ces deux mesures devraient nous donner un graphique plus lisible.\n",
    "\n",
    "Troisièmement, nous utiliserons `Plotly` pour rendre notre graphique interactif, ce qui est idéal pour l'exploration.\n",
    "\n",
    "Nous définirons notre propre helper fonction `show_forecast` et l'appellerons (pour en savoir plus sur son fonctionnement, veuillez vous référer aux commentaires dans le code et la [documentation](https://plot.ly/python/)):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def show_forecast(cmp_df, num_predictions, num_values, title):\n",
    "    \"\"\"Visualize the forecast.\"\"\"\n",
    "\n",
    "    def create_go(name, column, num, **kwargs):\n",
    "        points = cmp_df.tail(num)\n",
    "        args = dict(name=name, x=points.index, y=points[column], mode=\"lines\")\n",
    "        args.update(kwargs)\n",
    "        return go.Scatter(**args)\n",
    "\n",
    "    lower_bound = create_go(\n",
    "        \"Lower Bound\",\n",
    "        \"yhat_lower\",\n",
    "        num_predictions,\n",
    "        line=dict(width=0),\n",
    "        marker=dict(color=\"444\"),\n",
    "    )\n",
    "    upper_bound = create_go(\n",
    "        \"Upper Bound\",\n",
    "        \"yhat_upper\",\n",
    "        num_predictions,\n",
    "        line=dict(width=0),\n",
    "        marker=dict(color=\"444\"),\n",
    "        fillcolor=\"rgba(68, 68, 68, 0.3)\",\n",
    "        fill=\"tonexty\",\n",
    "    )\n",
    "    forecast = create_go(\n",
    "        \"Forecast\", \"yhat\", num_predictions, line=dict(color=\"rgb(31, 119, 180)\")\n",
    "    )\n",
    "    actual = create_go(\"Actual\", \"y\", num_values, marker=dict(color=\"red\"))\n",
    "\n",
    "    # In this case the order of the series is important because of the filling\n",
    "    data = [lower_bound, upper_bound, forecast, actual]\n",
    "\n",
    "    layout = go.Layout(yaxis=dict(title=\"Posts\"), title=title, showlegend=False)\n",
    "    fig = go.Figure(data=data, layout=layout)\n",
    "    iplot(fig, show_link=False)\n",
    "\n",
    "\n",
    "show_forecast(cmp_df, prediction_size, 100, \"New posts on Medium\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "À première vue, la prédiction des valeurs moyennes par notre modèle semble raisonnable. La valeur élevée de MAPE que nous avons obtenue ci-dessus peut s'expliquer par le fait que le modèle n'a pas réussi à saisir l'amplitude croissante de pic-à-pic (peak-to-peak) d'une faible saisonnalité. \n",
    "\n",
    "En outre, nous pouvons conclure du graphique ci-dessus que de nombreuses valeurs réelles se trouvent en dehors de l'intervalle de confiance. Prophet peut ne pas convenir aux séries chronologiques avec une variance instable, du moins lorsque les paramètres par défaut sont utilisés. Nous allons essayer de résoudre ce problème en appliquant une transformation à nos données."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 4. Transformation Box-Cox"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Jusqu'à présent, nous avons utilisé Prophet avec les paramètres par défaut et les données d'origine. Nous laisserons les paramètres du modèle seuls. Mais malgré cela, nous avons encore des progrès à faire. Dans cette section, nous appliquerons la [Box–Cox transformation](http://onlinestatbook.com/2/transformations/box-cox.html) à notre série originale. Voyons où cela nous mènera.\n",
    "\n",
    "Quelques mots sur cette transformation. Il s'agit d'une transformation de données monotone qui peut être utilisée pour stabiliser la variance. Nous utiliserons la transformation Box-Cox à un paramètre, qui est définie par l'expression suivante:\n",
    "\n",
    "$$\n",
    "\\begin{equation}\n",
    "  boxcox^{(\\lambda)}(y_{i}) = \\begin{cases}\n",
    "    \\frac{\\displaystyle y_{i}^{\\lambda} - 1}{\\displaystyle \\lambda} &, \\text{if $\\lambda \\neq 0$}.\\\\\n",
    "    ln(y_{i}) &, \\text{if $\\lambda = 0$}.\n",
    "  \\end{cases}\n",
    "\\end{equation}\n",
    "$$\n",
    "\n",
    "Nous devrons implémenter l'inverse de cette fonction afin de pouvoir restaurer l'échelle de données d'origine. Il est facile de voir que l'inverse est défini comme:\n",
    "\n",
    "$$\n",
    "\\begin{equation}\n",
    "  invboxcox^{(\\lambda)}(y_{i}) = \\begin{cases}\n",
    "    e^{\\left (\\frac{\\displaystyle ln(\\lambda y_{i} + 1)}{\\displaystyle \\lambda} \\right )} &, \\text{if $\\lambda \\neq 0$}.\\\\\n",
    "    e^{y_{i}} &, \\text{if $\\lambda = 0$}.\n",
    "  \\end{cases}\n",
    "\\end{equation}\n",
    "$$\n",
    "\n",
    "La fonction correspondante en Python est implémentée comme suit:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def inverse_boxcox(y, lambda_):\n",
    "    return np.exp(y) if lambda_ == 0 else np.exp(np.log(lambda_ * y + 1) / lambda_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Tout d'abord, nous préparons notre jeu de données en définissant son index:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df2 = train_df.copy().set_index(\"ds\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Ensuite, nous appliquons la fonction `stats.boxcox` de` Scipy`, qui applique la transformation Box – Cox. Dans notre cas, il renverra deux valeurs. La première est la série transformée et la seconde est la valeur trouvée de $\\lambda$ qui est optimale en termes de maximum de log-vraisemblance (maximum log-likelihood):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df2[\"y\"], lambda_prophet = stats.boxcox(train_df2[\"y\"])\n",
    "train_df2.reset_index(inplace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous créons un nouveau modèle `Prophet` et répétons le cycle d'ajustement de prévision que nous avons déjà fait ci-dessus:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m2 = Prophet()\n",
    "m2.fit(train_df2)\n",
    "future2 = m2.make_future_dataframe(periods=prediction_size)\n",
    "forecast2 = m2.predict(future2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "À ce stade, nous devons inverser la transformation de Box – Cox avec notre fonction inverse et la valeur connue de $\\lambda$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for column in [\"yhat\", \"yhat_lower\", \"yhat_upper\"]:\n",
    "    forecast2[column] = inverse_boxcox(forecast2[column], lambda_prophet)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Ici, nous allons réutiliser nos outils pour faire le dataframe de comparaison et calculer les erreurs:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cmp_df2 = make_comparison_dataframe(df, forecast2)\n",
    "for err_name, err_value in calculate_forecast_errors(cmp_df2, prediction_size).items():\n",
    "    print(err_name, err_value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "On peut donc affirmer avec certitude que la qualité du modèle s'est améliorée. \n",
    "\n",
    "Enfin, tracons nos performances précédentes avec les derniers résultats côte à côte. Notez que nous utilisons `prediction_size` pour le troisième paramètre afin de zoomer sur l'intervalle prévu:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show_forecast(cmp_df, prediction_size, 100, \"No transformations\")\n",
    "show_forecast(cmp_df2, prediction_size, 100, \"Box–Cox transformation\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous voyons que la prévision des changements hebdomadaires dans le deuxième graphique est beaucoup plus proche des valeurs réelles maintenant."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 5. Résumé"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous avons jeté un coup d'œil à *Prophet*, une bibliothèque de prévisions open source spécifiquement destinée aux séries chronologiques commerciales. Nous avons également effectué des exercices pratiques de prévision des séries chronologiques.\n",
    "\n",
    "Comme nous l'avons vu, la bibliothèque Prophet ne fait pas de merveilles et ses prédictions prêtes à l'emploi ne sont pas [idéales](https://en.wikipedia.org/wiki/No_free_lunch_in_search_and_optimization). Il appartient toujours au data scientist d'explorer les résultats des prévisions, d'ajuster les paramètres du modèle et de transformer les données si nécessaire.\n",
    "\n",
    "Toutefois, cette bibliothèque est conviviale et facilement personnalisable. La seule possibilité de prendre en compte les jours anormaux connus de l'analyste à l'avance peut faire la différence dans certains cas\n",
    "\n",
    "Dans l'ensemble, la bibliothèque Prophet vaut la peine de faire partie de votre boîte à outils analytiques."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 6. Références"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "- Official [Prophet repository](https://github.com/facebookincubator/prophet) on GitHub.\n",
    "- Official [Prophet documentation](https://facebookincubator.github.io/prophet/docs/quick_start.html).\n",
    "- Sean J. Taylor, Benjamin Letham [\"Forecasting at scale\"](https://facebookincubator.github.io/prophet/static/prophet_paper_20170113.pdf) — scientific paper explaining the algorithm which lays the foundation of `Prophet`.\n",
    "- [Forecasting Website Traffic Using Facebook’s Prophet Library](http://pbpython.com/prophet-overview.html) — `Prophet` overview with an example of website traffic forecasting.\n",
    "- Rob J. Hyndman, George Athanasopoulos [\"Forecasting: principles and practice\"](https://www.otexts.org/fpp) – a very good online book about time series forecasting."
   ]
  }
 ],
 "metadata": {
  "hide_input": false,
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  },
  "nbTranslate": {
   "displayLangs": [
    "*"
   ],
   "hotkey": "alt-t",
   "langInMainMenu": true,
   "sourceLang": "en",
   "targetLang": "fr",
   "useGoogleTranslate": true
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Plan de l'article",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
